﻿/*
Copyright (c) 2008 William Duff

Permission is hereby granted, free of charge, to any person obtaining a copy 
of this software and associated documentation files (the "Software"), to deal 
in the Software without restriction, including without limitation the rights 
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all 
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 
SOFTWARE.
*/

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.WindowsLive.PublishPlugins;
using System.Xml;
using System.Windows.Forms;
using System.IO;
using System.Threading;
using System.Diagnostics;
using LiveUpload.Facebook.Properties;
using System.Globalization;

namespace LiveUpload.Facebook
{
    class FacebookPublishPlugin : IPublishPlugin
    {
        /// <summary>
        /// Indicates whether or not a publishing plug-in has publishing results available to 
        /// display to the user.
        /// </summary>
        /// <param name="sessionXml">
        /// An XmlDocument containing session specific information for this publish session.
        /// </param>
        /// <returns>
        /// true if the publishing plug-in has publishing results to display; otherwise, false.
        /// </returns>
        public bool HasPublishResults(XmlDocument sessionXml)
        {
            // Encapsulate sessionXml.
            SessionData sessionData = new SessionData(sessionXml);

            // Return true if we have a link to an album.
            if (sessionData.Album != null && !string.IsNullOrEmpty(sessionData.Album.Link))
            {
                return true;
            }

            // Return true if we have a link to a video.
            foreach (Item uploadedItem in sessionData.ItemSet)
            {
                if (uploadedItem.PerceivedType == "video" && uploadedItem.Link != string.Empty)
                {
                    return true;
                }
            }

            return false;
        }

        /// <summary>
        /// Indicates whether or not a publishing plug-in has summary information available to 
        /// display to the user.
        /// </summary>
        /// <param name="sessionXml">
        /// An XmlDocument containing session specific information for this publish session.
        /// </param>
        /// <returns>
        /// true if the publishing plug-in has summary information to display; otherwise, false.
        /// </returns>
        public bool HasSummaryInformation(XmlDocument sessionXml)
        {
            // Encapsulate sessionXml.
            SessionData sessionData = new SessionData(sessionXml);

            // Return true if there is at least one item in the log.
            return sessionData.Log.Count > 0;
        }

        /// <summary>
        /// Directs the publishing plug-in to display publishing results.
        /// </summary>
        /// <param name="sessionXml">
        /// An XmlDocument containing session specific information for this publish session.
        /// </param>
        public void LaunchPublishResults(XmlDocument sessionXml)
        {
            // Encapsulate sessionXml.
            SessionData sessionData = new SessionData(sessionXml);

            // Direct the user to the album URL.
            if (sessionData.Album != null && !string.IsNullOrEmpty(sessionData.Album.Link))
            {
                FacebookPublishPlugin.OpenDefaultBrowser(sessionData.Album.Link);
            }

            // Launch each video uploaded independently.
            foreach (Item uploadedItem in sessionData.ItemSet)
            {
                if (uploadedItem.PerceivedType == "video" && !string.IsNullOrEmpty(uploadedItem.Link))
                {
                    FacebookPublishPlugin.OpenDefaultBrowser(uploadedItem.Link);
                }
            }
        }

        /// <summary>
        /// Directs the publishing plug-in to publish a specified media object.
        /// </summary>
        /// <param name="parentWindow">
        /// A handle to the parent window that will be used to display any UI to the user.
        /// </param>
        /// <param name="mediaObjectId">
        /// A string identifying the media object to be published. 
        /// </param>
        /// <param name="stream">
        /// A stream for the contents of the media object to be published.
        /// </param>
        /// <param name="sessionXml">
        /// An XmlDocument containing session specific information for this publish session.
        /// </param>
        /// <param name="publishProperties">
        /// A reference an IPublishProperties interface, which can be used to access a media 
        /// object's metadata store.
        /// </param>
        /// <param name="callback">
        /// A reference to an IPublishProgressCallback callback interface that can be used to 
        /// communicate progress information to the user.
        /// </param>
        /// <param name="cancelEvent">
        /// A thread synchronization event that will signal in the event a user clicks the cancel
        /// button during the upoad.
        /// </param>
        /// <returns>
        /// true if the specified media object was successfully published; otherwise, false.
        /// </returns>
        public bool PublishItem(IWin32Window parentWindow, string mediaObjectId, Stream stream,
            XmlDocument sessionXml, IPublishProperties publishProperties,
            IPublishProgressCallback callback, EventWaitHandle cancelEvent)
        {
            // Encapsulate sessionXml.
            SessionData sessionData = new SessionData(sessionXml);

            Item item = sessionData.ItemSet.Find(delegate(Item mediaObject)
            {
                return mediaObject.Id == mediaObjectId;
            });

            bool success;

            if (item.PerceivedType == "image")
            {
                success = PublishPhoto(stream, callback, sessionData, item);
            }
            else if (item.PerceivedType == "video")
            {
                success = PublishVideo(stream, callback, sessionData, item);
            }
            else
            {
                string error = string.Format(Resources.UploadFailure, item.OriginalFileName, 
                    Resources.UnrecognizedFileType);

                Trace.WriteLine(error);

                sessionData.Log.Add(new LogItem(LogItem.Outcome.Unsuccessful, error));
                sessionData.Save();

                success = false;
            }

            Trace.Flush();

            return success;
        }

        /// <summary>
        /// Directs the publishing plug-in to publish a specified photo.
        /// </summary>
        /// <returns>
        /// true if the upload was a success and false otherwise.
        /// </returns>
        private static bool PublishPhoto(Stream stream, IPublishProgressCallback callback, 
            SessionData sessionData, Item item)
        {
            Photo uploadedPhoto = null;

            try
            {
                // Open the upload stream.
                UploadContext cookie =
                    FacebookService.PrepareUpload(sessionData.User, sessionData.Album.Id, stream,
                    item);

                // Upload the file in chunks, updating the UI in between each chunk.
                while (cookie.UploadChunk())
                {
                    callback.SetPublishProgress(cookie.PercentComplete);

                    if (callback.Canceled())
                    {
                        return false;
                    }
                }

                // Close the upload stream and get the response from Facebook.
                uploadedPhoto = FacebookService.GetUploadedPhoto(cookie);

                // If we have any person tags for this photo, now is the time to set them.
                List<PhotoTag> photoTags = new List<PhotoTag>();
                foreach (PersonTag personTag in item.PersonTagSet)
                {
                    int linkIndex = sessionData.AccountLinks.FindIndex(delegate(AccountLink accountLinkMatch)
                    {
                        return personTag.Name == accountLinkMatch.PersonTag.Name;
                    });

                    if (linkIndex >= 0)
                    {
                        Friend facebookFriend = sessionData.AccountLinks[linkIndex].FacebookFriend;

                        // Found a match, add it.
                        PhotoTag photoTag = new PhotoTag(uploadedPhoto.Id, facebookFriend.UID, facebookFriend.Name,
                            personTag.X, personTag.Y, uploadedPhoto.CreatedUnixTimestamp);
                        photoTags.Add(photoTag);
                    }
                }

                if (photoTags.Count > 0)
                {
                    FacebookService.AddTags(sessionData.User, photoTags);
                }
            }
            catch (FacebookException e)
            {
                string error = string.Format(Resources.UploadFailure, item.OriginalFileName,
                    e.Message);

                Trace.WriteLine(error);

                sessionData.Log.Add(new LogItem(LogItem.Outcome.Unsuccessful, error));
                sessionData.Save();

                return false;
            }

            // If we got here, the upload was a success.
            string message = string.Format(Resources.UploadSuccess, item.OriginalFileName);

            Trace.WriteLine(message);

            sessionData.Log.Add(new LogItem(LogItem.Outcome.Successful, message));
            sessionData.Save();

            return true;
        }

        /// <summary>
        /// Directs the publishing plug-in to publish a specified video.
        /// </summary>
        /// <returns>
        /// true if the upload was a success and false otherwise.
        /// </returns>
        private bool PublishVideo(Stream stream, IPublishProgressCallback callback, SessionData sessionData, Item item)
        {
            // Check video limits!
            Video uploadedVideo = null;

            try
            {
                // Open the upload stream.
                UploadContext cookie =
                    FacebookService.PrepareUpload(sessionData.User, string.Empty, stream, item);

                // Upload the file in chunks, updating the UI in between each chunk.
                while (cookie.UploadChunk())
                {
                    callback.SetPublishProgress(cookie.PercentComplete);

                    if (callback.Canceled())
                    {
                        return false;
                    }
                }

                // Close the upload stream and get the response from Facebook.
                uploadedVideo = FacebookService.GetUploadedVideo(cookie);

                if (!string.IsNullOrEmpty(uploadedVideo.Link))
                {
                    if(uploadedVideo.Link.StartsWith("/"))
                    {
                        // Quick bug fix, Facebook is returning only the end of the URL right now (8/5/2009)...
                        uploadedVideo.Link = "http://www.facebook.com" + uploadedVideo.Link;
                    }

                    item.Link = uploadedVideo.Link;
                }
            }
            catch (FacebookException e)
            {
                string error = string.Format(Resources.UploadFailure, item.OriginalFileName,
                    e.Message);

                Trace.WriteLine(error);

                sessionData.Log.Add(new LogItem(LogItem.Outcome.Unsuccessful, error));
                sessionData.Save();

                return false;
            }

            // If we got here, the upload was a success.
            string message = string.Format(Resources.UploadSuccess, item.OriginalFileName);

            Trace.WriteLine(message);

            sessionData.Log.Add(new LogItem(LogItem.Outcome.Successful, message));
            sessionData.Save();

            return true;
        }

        /// <summary>
        /// Directs the publishing plug-in to show its configuration UI.
        /// </summary>
        /// <param name="parentWindow">
        /// A handle to the parent window that will be used to display any UI to the user.
        /// </param>
        /// <param name="sessionXml">
        /// An XmlDocument containing session specific information for this publish session.
        /// </param>
        /// <param name="persistXml">
        /// An XmlDocument containing persistent information for this plug-in.
        /// </param>
        /// <param name="publishProperties">
        /// A reference an IPublishProperties interface, which can be used to access a media 
        /// object's metadata store.
        /// </param>
        /// <returns>
        /// true if the publishing operation should continue; false if there is an error, or the 
        /// user cancelled the publishing operation.
        /// </returns>
        public bool ShowConfigurationSettings(IWin32Window parentWindow, XmlDocument sessionXml,
            XmlDocument persistXml, IPublishProperties publishProperties)
        {
            // Encapsulate sessionXml and persistXml.
            SessionData sessionData = new SessionData(sessionXml);
            PersistentData persistData = new PersistentData(persistXml);

            Trace.Listeners.Clear();
            Trace.Listeners.Add(new LiveUploadFacebookTraceListener());

            ConfigurationSettings configurationSettings = 
                new ConfigurationSettings(sessionData, persistData, publishProperties);
            DialogResult result = configurationSettings.ShowDialog(parentWindow);

            Trace.Flush();

            return result == DialogResult.OK;
        }

        /// <summary>
        /// Directs the publishing plug-in to display summary information.
        /// </summary>
        /// <param name="parentWindow">
        /// A handle to the parent window that will be used to display the summary information to
        /// the user.
        /// </param>
        /// <param name="sessionXml">
        /// An XmlDocument containing session specific information for this publish session.
        /// </param>
        public void ShowSummaryInformation(IWin32Window parentWindow, XmlDocument sessionXml)
        {
            // Encapsulate sessionXml.
            SessionData sessionData = new SessionData(sessionXml);

            SummaryForm summaryForm = new SummaryForm(sessionData);
            summaryForm.ShowDialog(parentWindow);
        }

        /// <summary>
        /// Opens the default browser to the given url. If no default browser exists, shows
        /// an error message to the user asking them to set a default browser.
        /// </summary>
        public static void OpenDefaultBrowser(string url)
        {
            try
            {
                Process.Start(url);
            }
            catch (System.ComponentModel.Win32Exception)
            {
                // No default browser is set.
                MessageBox.Show(Resources.DefaultBrowserError, Resources.PluginTitle, MessageBoxButtons.OK,
                    MessageBoxIcon.Error);
            }
        }

        /// <summary>
        /// If the plug-in was invoked by MovieMaker, then we'll want to add an extra line to each
        /// video description.
        /// </summary>
        public static bool InvokedByMovieMaker()
        {
            Process currentProcess = Process.GetCurrentProcess();
            if(currentProcess.MainModule.ModuleName.Equals(Resources.MovieMakerModuleName, StringComparison.OrdinalIgnoreCase))
            {
                return true;
            }

            return false;
        }
    }
}
